Skip to content

Re-anchor the exemption registry to symbols and pin it there - #257

Merged
Antawari merged 1 commit into
mainfrom
catrina/retire-the-line-anchors
Jul 28, 2026
Merged

Re-anchor the exemption registry to symbols and pin it there#257
Antawari merged 1 commit into
mainfrom
catrina/retire-the-line-anchors

Conversation

@Antawari

Copy link
Copy Markdown
Contributor

Retires the line-anchor class in exemptions.json and pins it shut.

exemptions.json anchored 44 of its 52 entries by line number. A line anchor is a pointer any edit moves, so inserting a single line above a registered noqa silently un-registered a blessed exemption and cf-exemptions then reported UNREGISTERED_SUPPRESSION against the suppression - naming the code as the culprit when the registry pointer is what rotted. That message is convincing, and it cost three lanes an afternoon.

Count-neutral. 52 entries in, 52 out, frozen_count 52, edited as surgical text (never json.dumps). The diff is +41/-41 on exemptions.json, every changed line is a symbol_or_line value, and the em-dash byte count is unchanged at 66 (counted as byte sequences, not grep -c, which counts lines). Zero source edits - no # noqa was touched.


1. Converted 41 of 44. Three are stuck.

Every suppression's enclosing symbol was resolved with the gate's own _symbol_spans / _enclosing_symbol, never by eye and never by regex. An entry was migrated only where the resulting (file, rule, symbol) triple resolves to exactly one live suppression - verified per entry, not assumed from a table.

# entry why it cannot be symbol-anchored
19 src/bonfire/git/scratch.py S105 line 55 Module level. The suppression sits outside any def/class, so _enclosing_symbol returns None. There is no symbol to name.
47 src/bonfire/onboard/server.py S101 line 446 Shared symbol. Both S101 suppressions resolve to FrontDoorServer._ws_handler.
48 src/bonfire/onboard/server.py S101 line 447 Same symbol as 446. One anchor would cover both.

Leaving these line-pinned is the finding, not the failure. For 47/48 a symbol anchor would kill one of the two entries (52 entries would stop mapping to 52 suppressions, with nothing saying so) and would turn a specific exemption into a blanket one: a future assert dropped into _ws_handler would be auto-blessed with nobody approving it.

What a correct fix would need - recommended, not taken. No new anchor format was invented.

  • 47/48 - the cheapest correct fix is a code change separating the two suppressions (one assert per helper, or one combined assert), which puts them in different enclosing symbols and needs no format change at all. The alternatives are kit-side: a compound anchor (FrontDoorServer._ws_handler#2) or a per-occurrence index field. Both belong to candyfactory-quality, not to this repo, and source edits were out of scope - so neither is this lane's to take.
  • 19 - needs either a kit-side module-level anchor token (<module> plus an index) or a code change moving the constant inside a function. Same ownership answer.

2. The 7 colliding (file, rule) pairs: 6 were safe, 1 was not

The brief's table proved (file, rule) collisions exist; it did not prove which of them share a symbol. Measured:

file rule lines -> enclosing symbols verdict
dispatch/security_hooks.py C901 539 _match_write_edit_sensitive_path / 843 _peel_one / 895 _unwrap / 1084 build_preexec_hook / 1121 build_preexec_hook._hook all five distinct - safe
dispatch/security_hooks.py PLR0915 1084 build_preexec_hook / 1121 build_preexec_hook._hook safe - names are qualified, and _enclosing_symbol takes the innermost span, so the nested _hook never collapses into its parent
handlers/merge_preflight.py BLE001 277 MergePreflightHandler.handle / 564 MergePreflightHandler._get_baseline_failures safe
handlers/sage_correction_bounce.py BLE001 320 SageCorrectionBounceHandler.handle / 539 SageCorrectionBounceHandler._run_correction_cycle safe
knowledge/backend.py BLE001 78 LanceDBBackend.query / 117 LanceDBBackend.get_by_source safe
onboard/server.py S101 446 FrontDoorServer._ws_handler / 447 FrontDoorServer._ws_handler COLLIDES - left line-pinned
scan/tech_scanner.py C901 72 _extract_pyproject_deps / 245 TechScanner._detect_frameworks safe

Only the adjacent pair collides, exactly as suspected. The other six resolve to different functions and migrated cleanly.

Correction worth carrying: the blanket-permission risk is already mechanically gated at kit altitude. cf-exemptions fails an entry that covers more than one live suppression (EXEMPTION_ENTRY_OVERLOADED). An ambiguous conversion therefore goes loudly red, not silently blanket. That does not make the conversion safe to do - it makes it impossible to land. Rodded below.

3. The new gate: tests/unit/test_exemption_anchors_are_drift_proof.py

The kit's overload check covers one of three failure modes. Two are ungated: nothing stops a new line anchor being introduced, and nothing notices a dead entry that covers no suppression at all (frozen_count == entries, so not even the ratchet-slack line prints). The new test pins all three, in this repo, on stdlib only.

  1. test_every_anchor_is_a_symbol_except_the_documented_line_pins - the set of line-anchored entries must equal _LINE_PINNED exactly. A re-introduced line anchor fails; a pin that becomes fixable and is not removed also fails, so the allowlist cannot go stale. Carries a non-vacuity assertion that the symbol-anchored population is non-empty.
  2. test_no_exemption_entry_covers_more_than_one_suppression - every entry, either anchor form, must resolve to exactly one live suppression. Catches ambiguity and dead entries.
  3. test_the_ambiguity_measure_reports_two_for_a_deliberately_ambiguous_anchor - control rod on the measure itself: a synthetic module with two S101 in one method must resolve to [3, 4], a unique anchor to [7], a line pin to [4]. The assertion is proved able to fail, not merely observed passing.
  4. test_the_local_resolver_agrees_with_the_installed_quality_kit - round trip. The resolver used by 1-3 must agree entry-for-entry with cf_quality.exemptions._scan_src / _matches wherever the kit is importable, so the mirror cannot drift from the gate it mirrors.

Proof it catches a deliberately ambiguous anchor. Point entry 47 at the shared symbol, then run the new test:

E  AssertionError: these exemption entries each cover MORE THAN ONE live suppression, so one entry
   silently blesses several sites and a future suppression of the same rule in that symbol would be
   auto-covered: entry 47 src/bonfire/onboard/server.py S101 anchor='FrontDoorServer._ws_handler'
   -> lines [446, 447]
E  assert not {47: [446, 447]}
FAILED tests/unit/...::test_every_anchor_is_a_symbol_except_the_documented_line_pins
FAILED tests/unit/...::test_no_exemption_entry_covers_more_than_one_suppression
2 failed, 2 passed

Proof it catches a re-introduced line anchor. Regress knowledge/backend.py BLE001 back to "78":

E  undocumented line anchors (re-anchor to the enclosing symbol): [('src/bonfire/knowledge/backend.py', 'BLE001', '78')]
E  stale _LINE_PINNED rows (entry is gone or now symbol-anchored, drop them): []
FAILED tests/unit/...::test_every_anchor_is_a_symbol_except_the_documented_line_pins
1 failed, 3 passed

Proof the kit's own overload check is real, not merely present in its source:

exemptions.json: EXEMPTION_ENTRY_OVERLOADED: exemptions.json entry 47 covers 2 live suppressions -
each suppression needs its own reasoned entry (1:1, ratchet-true)
cf-exemptions: FAIL (1 violation(s))     exit 1

4. Control rods, both halves, verbatim

Drift-proof half - A/B/A on the named failure. The same 30 inserted lines above the suppressions in knowledge/backend.py, scan/tech_scanner.py, dispatch/security_hooks.py and handlers/merge_preflight.py, applied to two trees:

=== ROD B (this branch, 30 lines inserted) ===
EXIT=0
=== EXEMPTION RATCHET: 52 entries / frozen_count 52 ===
cf-exemptions: OK
=== ROD B' (bare origin/main, SAME 30 lines inserted) ===
EXIT=1
src/bonfire/dispatch/security_hooks.py:569:  UNREGISTERED_SUPPRESSION: 'C901' ...
src/bonfire/dispatch/security_hooks.py:873:  UNREGISTERED_SUPPRESSION: 'C901' ...
src/bonfire/dispatch/security_hooks.py:925:  UNREGISTERED_SUPPRESSION: 'C901' ...
src/bonfire/dispatch/security_hooks.py:1114: UNREGISTERED_SUPPRESSION: 'C901' ...
src/bonfire/dispatch/security_hooks.py:1114: UNREGISTERED_SUPPRESSION: 'PLR0915' ...
src/bonfire/dispatch/security_hooks.py:1151: UNREGISTERED_SUPPRESSION: 'C901' ...
src/bonfire/dispatch/security_hooks.py:1151: UNREGISTERED_SUPPRESSION: 'PLR0915' ...
src/bonfire/dispatch/security_hooks.py:1179: UNREGISTERED_SUPPRESSION: 'S101' ...
src/bonfire/handlers/merge_preflight.py:307:  UNREGISTERED_SUPPRESSION: 'BLE001' ...
src/bonfire/handlers/merge_preflight.py:594:  UNREGISTERED_SUPPRESSION: 'BLE001' ...
src/bonfire/knowledge/backend.py:108:         UNREGISTERED_SUPPRESSION: 'BLE001' ...
src/bonfire/knowledge/backend.py:147:         UNREGISTERED_SUPPRESSION: 'BLE001' ...
src/bonfire/scan/tech_scanner.py:102:         UNREGISTERED_SUPPRESSION: 'C901' ...
src/bonfire/scan/tech_scanner.py:275:         UNREGISTERED_SUPPRESSION: 'C901' ...
cf-exemptions: FAIL (14 violation(s))

Same insertion, same gate, same command: main reports 14 phantom culprits, this branch reports none. That is the defect, measured.

Not-defanged half. A fresh, never-registered # noqa: BLE001 injected into knowledge/chunker.py on this branch:

EXIT=1
src/bonfire/knowledge/chunker.py:199: UNREGISTERED_SUPPRESSION: 'BLE001' suppression has no matching
entry in exemptions.json - a self-issued suppression is not an exemption
cf-exemptions: FAIL (1 violation(s))

The gate still bites. Every rod ran on scratch copies; the worktree was never dirtied.

5. complexipy-snapshot.json

ce1dbdc0ff983e828e4a9ba69998fe67 - byte-identical to origin/main (git show origin/main:complexipy-snapshot.json | md5sum matches), 20 entries, non-empty. Verified before the gate runs, after each of the three cf-gate runs, and as the last action.

6. Gate verdict - the delta is zero

cf-gate run from an environment with bonfire and the kit's [dev] extra installed, on this branch and on a fresh export of bare origin/main prepared identically:

this branch      : FAIL - 2 of 11   (mypy, complexipy)
bare origin/main : FAIL - 2 of 11   (mypy, complexipy)

The red-gate detail block is byte-identical between the two (diff clean): MYPY_NEW_ERRORS (the local-kit artefact) and COMPLEXIPY_FAILED. Nine gates green on both, including cf-exemptions, cf-file-budget and pytest. Nothing in this PR is red.

Local suite: 5777 passed, 4 skipped, 37 xfailed, 20 xpassed. ruff check and ruff format --check clean across src/ and tests/.

7. One budget raise, surfaced not taken silently

tests/unit 73777 -> 74023 (+246), the exact measured line count of the new test file, recorded in package_raises with its reasoning. Explicitly rejected: parking the file at unbudgeted tests/ root (two earlier raises in that same file already rejected the unbudgeted tree as dodging the ratchet, and the same answer applies here); a purpose entry; dropping the kit-agreement test to save ~25 lines. The file was tightened from 261 to 246 lines before the number was taken. Nothing was added to or removed from exemptions.json.

8. What I did NOT prove

  • The kit version CI actually runs. EXEMPTION_ENTRY_OVERLOADED was proved to fire in the locally installed kit (candyfactory-quality at 74b346c, editable install). CI pins quality-gate.yml@adaa4ebf...; that SHA's copy of exemptions.py was not read. "The ambiguity gate is enforced in CI" is inferred from the local kit, not measured against the pin.
  • Test 4 is a skip in this repo's own CI test job. cf_quality is not a bonfire dev dependency, so test_the_local_resolver_agrees_with_the_installed_quality_kit passes where the kit is installed and skips under a plain pip install -e ".[dev]". Tests 1-3 always run. The round trip is a local/kit-env check, not a CI-enforced one.
  • Symbol anchors are insertion-proof, not rename-proof. Renaming an enclosing def or class un-registers its exemptions exactly as a line shift used to, and the gate will again name the suppression as the culprit. This PR does not close that; it removes the far more frequent trigger. Worth a follow-up note in the gate's own error text.
  • No source behaviour was exercised. Zero # noqa edits, so nothing here can change what ruff suppresses - but neither was it independently re-verified that all 52 suppressions are still needed.
  • Nothing ran in a box or in CI. The local-equals-CI claim rests entirely on the bare-origin/main delta method.
  • The 3 stuck entries were not proved unfixable in principle - only that no anchor fixes them under the current format without a code change or a kit change, neither of which was in scope.

Out of scope, one sentence: cf-file-budget reports two shrink opportunities untouched by this PR - tests/unit/test_engine_gates.py 621 -> 615 and tests/unit/test_engine_pipeline.py 1895 -> 1894 - which would hand the next lane 7 lines of headroom back.

exemptions.json anchored 44 of its 52 entries by line number. A line anchor
is a pointer any edit moves: inserting a single line above a registered
noqa silently un-registered a blessed exemption, and cf-exemptions then
reported UNREGISTERED_SUPPRESSION against the suppression, naming the code
as the culprit when the registry pointer was what rotted.

Convert 41 of the 44 to the enclosing symbol, resolved with the gate's own
_symbol_spans/_enclosing_symbol rather than by eye. Count-neutral: 52
entries in, 52 out, frozen_count untouched, and the only lines that differ
are the 41 symbol_or_line values.

Three entries stay line-pinned because a symbol anchor provably cannot
identify them uniquely: git/scratch.py S105 sits at module level with no
enclosing symbol, and onboard/server.py's two S101 suppressions share one
enclosing method, where a symbol anchor would cover both and blanket-bless
any future assert in that handler.

Add tests/unit/test_exemption_anchors_are_drift_proof.py, the durable half:
every entry must be symbol-anchored except an explicit map of the three
stuck ones (so a re-introduced line anchor fails and a stale pin fails),
every entry must resolve to exactly one live suppression (so an ambiguous
anchor and a dead entry both fail), and the small resolver those checks use
must agree entry-for-entry with cf_quality's own matcher wherever the kit
is importable. A fourth test rods the measure itself against a synthetic
module with two suppressions in one method.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Antawari
Antawari merged commit 6b58c58 into main Jul 28, 2026
4 checks passed
@Antawari
Antawari deleted the catrina/retire-the-line-anchors branch July 28, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant